package com.cellulam.framework.datasource;

import com.cellulam.framework.core.annotations.TransTargetDataSource;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 切面 - 数据源切换 <br>
 * 按照包名 - 目标数据源 方式切换
 */
@Deprecated
public abstract class ServiceDSAspect {

    private static final Logger logger = LoggerFactory.getLogger(ServiceDSAspect.class);

    private Map<String, String> packDsNames = new HashMap<>();

    public ServiceDSAspect() {

    }

    /**
     * 切面 - 基础CrudService <br>
     *
     * @Around("baseCrudPointcut()")
     */
    @Pointcut("execution(* com.cellulam.framework.datasource.service.BaseCrudService+.*(..))")
    public void baseCrudPointcut() {
    }

    /**
     * 切面 - 基础dtoService
     */
    @Pointcut("execution(* com.cellulam.framework.datasource.service.BaseDtoService+.*(..))")
    public void baseDtoPointcut() {
    }

    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        logger.debug("pjp target class name is {}", getTargetClazzName(pjp));
        String packName = getDataSourceName(pjp);
        boolean dataSourceSwitched = false;
        if (StringUtils.isNotEmpty(packName)
                && !StringUtils.equals(packName, StackDataSource.getCurrentTargetKey())) {

            StackDataSource.push(packName);
            dataSourceSwitched = true;
        }

        try {
            return pjp.proceed();
        } catch (Throwable e) {
            throw e;
        } finally {
            if (dataSourceSwitched) {
                StackDataSource.popupAndNext();
            }
        }
    }

    protected String getTargetClazzName(ProceedingJoinPoint pjp) {
        Class<?> clz = pjp.getTarget().getClass();
        if (Modifier.isAbstract(clz.getModifiers())) {
            return clz.getCanonicalName();
        } else {
            return clz.getCanonicalName();
        }
    }

    protected String getDataSourceName(ProceedingJoinPoint pjp) {
        String ds = null;
        TransTargetDataSource readAnno = getReadAnno(pjp);
        if (readAnno != null) {
            ds = StringUtils.trimToEmpty(readAnno.value());
        }

        if (StringUtils.isEmpty(ds)) {
            String packName = "";
            String targetClazzName = getTargetClazzName(pjp);
            if (targetClazzName.contains("$")) {
                targetClazzName = targetClazzName.substring(0, targetClazzName.indexOf('$'));
            }

            if (getServicePackPattern() != null) {
                Matcher mat = getServicePackPattern().matcher(targetClazzName);
                if (mat.find()) {
                    packName = mat.group(1);
                }
            }
            if (StringUtils.isEmpty(packName)) {
                packName = getSubPackageName(targetClazzName);
            }
            ds = packDsNames.getOrDefault(packName, packName);
        }

        logger.debug("{} matched {}.{}, target datasource is {}",
                StringUtils.isNotEmpty(ds) ? "" : "not",
                pjp.getTarget().getClass().getSimpleName(), getMethod(pjp).getName(), ds);

        return ds;
    }

    protected String getSubPackageName(String className) {
        int lastDotIndex = className.lastIndexOf('.');
        int lastSecondDotIndex = className.lastIndexOf('.', lastDotIndex - 1);
        return className.substring(lastSecondDotIndex + 1, lastDotIndex);
    }

    protected Method getMethod(ProceedingJoinPoint pjp) {
        MethodSignature signature = ((MethodSignature) pjp.getSignature());
        return signature.getMethod();
    }

    protected TransTargetDataSource getReadAnno(ProceedingJoinPoint pjp) {
        TransTargetDataSource readAnno = getMethod(pjp).getAnnotation(TransTargetDataSource.class);
        if (readAnno == null) {
            readAnno = pjp.getTarget().getClass().getAnnotation(TransTargetDataSource.class);
        }
        return readAnno;
    }

    /**
     * 设置一个包名对应的数据源名称
     *
     * @param packName
     * @param dsName
     */
    public void putPackDsName(String packName, String dsName) {
        packDsNames.put(packName, dsName);
        logger.info("add mapping packName#{} -> dataSource Name#{}", packName, dsName);
    }

    public abstract Pattern getServicePackPattern();

}
